<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
// +----------------------------------------------------------------------+
// | PHP Version 4                                                        |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2003 The PHP Group                                |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.02 of the PHP license,      |
// | that is bundled with this package in the file LICENSE, and is        |
// | available at through the world-wide-web at                           |
// | http://www.php.net/license/2_02.txt.                                 |
// | If you did not receive a copy of the PHP license and are unable to   |
// | obtain it through the world-wide-web, please send a note to          |
// | license@php.net so we can mail you a copy immediately.               |
// +----------------------------------------------------------------------+
// | Author: Stefan Neufeind <pear.neufeind@speedpartner.de>              |
// +----------------------------------------------------------------------+
//
// $Id$

/**
* Package for handling (encoding/decoding) of international domainnames using Punycode
*
* The Punycode-representation is now the official standard for international domainnames.
* All language-specific / national characters are encoded into what is known as "Punycode".
* The Punycode comprises of "simple" ASCII-characters and therefor allows transparent use
* in DNS, webservers, mailers, proxies etc.
* All future browsers/email-clients are strongly encouraged to implement Punycode.
*
* The classes are based on documentation from RFC3454 (Stringprep), RFC3490 (IDNA),
* RFC3491 (Nameprep) and RFC3492 (Punycode).
*
* @author   Stefan Neufeind <pear.neufeind@speedpartner.de>
* @package  I18N_Punycode
* @category i18n
* @license  The PHP License, version 2.02
*/

// import conversion-tables
list($RFC3454_TABLE_B1_ucs4, $RFC3454_TABLE_B2_ucs4) = unserialize(file_get_contents(dirname(__FILE__).'/rfcC3454_table_b1b2.dat'));

/**
* Class for encoding/decoding on international domainnames using Punycode; ASCII-mode
*
* !! Please note that Punycode is meant to be used with Unicode-strings. This ASCII-class
* is just provided for convenience if you can be sure you just need to encode strings with
* usual ASCII-characters. If possible (e.g. supported by your webserver) it's highly recommended
* that you use I18N_Punycode_Unicode. !!
*
* @author   Stefan Neufeind <pear.neufeind@speedpartner.de>
* @package  I18N_Punycode
* @access   public
* @see      I18N_Punycode_Unicode
*/
class I18N_Punycode_ASCII
{
    /**
    * @access private
    */
    var $_punycode_prefix = "xn--";
    /**
    * @access private
    */
    var $_punycode_delimiter = "-";
    /**
    * @access private
    */
    var $_base = 36;
    /**
    * @access private
    */
    var $_tmin = 1;
    /**
    * @access private
    */
    var $_tmax = 26;
    /**
    * @access private
    */
    var $_initial_bias = 72;
    /**
    * @access private
    */
    var $_initial_n = 0x80;

    /**
    * Create a new I18N_Punycode_ASCII object
    *
    * This function exists because the Unicode-version of this class
    * (I18N_Punycode_Unicode) can interface with various unicode-extensions
    * and needs to be able to return error-objects when a new object is created.
    *
    * @return mixed   newly created I18N_Punycode_ASCII object, or a I18N_Punycode error code
    * @access public
    */
    function &factory()
    {
        $obj = new I18N_Punycode_ASCII();

        return $obj;
    }

    /**
    * Calculates new bias for each step in the calculation-process
    *
    * @param  int     $delta
    * @param  int     $numpoints
    * @param  bool    $firsttime  is this function called the first time?
    * @return int                 new bias
    * @access private
    */
    function _adapt($delta, $numpoints, $firsttime)
    {
        $_damp = 700; // constant value
        $_skew = 38;  // constant value
        
        if ($firsttime){
            $delta = (int) ($delta / $_damp);
        }else{
            $delta = (int) ($delta / 2);
        }
        $delta = $delta + (int) ($delta / $numpoints);
        $k = 0;
        while ($delta > (($this->_base - $this->_tmin) * $this->_tmax) / 2){
            $delta = (int) ($delta / ($this->_base - $this->_tmin));
            $k = $k + $this->_base;
        }
        return ($k + ((($this->_base - $this->_tmin + 1) * $delta) / ($delta + $_skew)));
    }

    /**
    * Check whether the given "codepoint" is "basic" and doesn't need to be encoded
    *
    * During encoding all basic codepoints are copied to the beginning of the output-string.
    * After that only the non-basic codepoints actually need to be encoded.
    *
    * @param  int     $codepoint   ASCII/Unicode-value for this char
    * @return bool                 was a basic codepoint given?
    * @access private
    */
    function _isBasicCodepoint($codepoint)
    {
        return (($codepoint >= 0) && ($codepoint <= 0x7F));
    }

    /**
    * Convert a code-point (given as it's integer value) to an appropriate value for calculations
    *
    * Only [0-0a-zA-Z] are valid codepoints
    *
    * @param  int     $codepoint   ASCII/Unicode-value for this char
    * @return int                  internal value (digit-value)
    * @access private
    * @see    _isBasicCodepoint()
    * @see    _digitValueToInt()
    */
    function _digitIntToValue($codepoint_int)
    {
        if (($codepoint_int >= ord('A')) && ($codepoint_int <= ord('Z'))){
            return $codepoint_int - ord('A');
        }
        if (($codepoint_int >= ord('a')) && ($codepoint_int <= ord('z'))){
            return $codepoint_int - ord('a');
        }
        if (($codepoint_int >= ord('0')) && ($codepoint_int <= ord('9'))){
            return $codepoint_int - ord('0') + 26;
        }
        // else
        echo "No digit-value for code-point";
        exit();
    }

    /**
    * Convert internal value (for calculations) to a code-point (given as integer-value)
    *
    * @param  int     $codepoint_int   Internal value to be converted
    * @return int                      Code-point (in the range of [0-9a-z])
    * @access private
    * @see    _isBasicCodepoint()
    * @see    _digitIntToValue()
    */
    function _digitValueToInt($codepoint_int)
    {
        if (($codepoint_int >= 0) && ($codepoint_int <= 25)){
            return $codepoint_int + ord('a');
        }
        if (($codepoint_int >= 26) && ($codepoint_int <= 35)){
            return $codepoint_int-26 + ord('0');
        }
        // else
        echo "No digit for code-point-int";
        exit();
    }

    /**
    * Apply nameprep transformation to the given string
    *
    * Nameprep replaces/removes various characters according to given tables.
    * These tables have been carefully designed with the fact in mind that
    * certain strings usually look quite similar or can be written in different
    * variations.
    *
    * @param  string  $input    String/name to be decoded
    * @return string            Decoded form of the given string
    * @access private
    */
    function _nameprep($input)
    {
        global $RFC3454_TABLE_B1_ucs4;
        global $RFC3454_TABLE_B2_ucs4;

        $output = "";
        $inputlength_absolute = strlen($input);
        for($character_count = 0; $character_count < $inputlength_absolute; $character_count ++) {
            $tempchar = "\x00\x00\x00".$input[$character_count]; // grab a char and extend to UCS-4 manually
            // two-step stringprep according to the nameprep-profile defined in RFC3491
            if (isset($RFC3454_TABLE_B1_ucs4[$tempchar])){
                $tempchar = $RFC3454_TABLE_B1_ucs4[$tempchar];
            }
            if (isset($RFC3454_TABLE_B2_ucs4[$tempchar])){
                $tempchar = $RFC3454_TABLE_B2_ucs4[$tempchar];
            }

            for ($counter=0; $counter<strlen($tempchar); $counter += 4) {
                $output .= $tempchar[$counter+3];
                // only take the last byte of the UCS-4-code
                // note: this is only okay for ASCII-characters, which are \x00\x00\x00 followed by a char
            }
        }

        return $output;
    }

    /**
    * Encode given string/domain name into it's Punycode-representation
    *
    * @param  string  $input    String/name to be encoded
    * @return string            Punycode form of the given string
    * @access private
    * @see    _punycodeDecode()
    */
    function _punycodeEncode($input)
    {
        // copy all basic code-points
        $output = "";
        for ($count = 0; $count < strlen($input); $count++){
            $tempchar = $input[$count];
            if ($this->_isBasicCodepoint(ord($tempchar))){
                $output .= $tempchar;
            }
        }

        $numBasicCodepointsInInput = strlen($output);
        // check for trivial case that there are no chars to encode
        if ($numBasicCodepointsInInput == strlen($input)){
            return $output;
        }

        if ($numBasicCodepointsInInput > 0){
            $output .= $this->_punycode_delimiter;
        }

        $n = $this->_initial_n;
        $delta = 0;
        $bias = $this->_initial_bias;
        $h = $b = $numBasicCodepointsInInput;

        while ($h < strlen($input)){
            // the minimum {non-basic} code point >= n in the input
            unset($m);
            $charcount = 0;

            do {
                $tempcode = ord($input[$charcount]);
                if (($tempcode >= $n) &&
                    (!isset($m) || ($tempcode <= $m))
                   ) {
                    $m = $tempcode;
                }
                $charcount++;
            } while ($charcount<strlen($input));

            $delta = $delta + ($m - $n) * ($h + 1); // fail on overflow
            $n = $m;

            for ($count = 0; $count < strlen($input); $count++){
                $c = ord($input[$count]);
                if ($c < $n){
                    $delta++; // fail on overflow
                }elseif ($c == $n){
                    $q = $delta;
                    $my_end = false;
                    $k = $this->_base;
                    do{
                        if ($k <= $bias){
                            $t = $this->_tmin;
                        }elseif ($k >= ($bias + $this->_tmax)){
                            $t = $this->_tmax;
                        }else{
                            $t = $k - $bias;
                        }

                        if ($q < $t){
                            $my_end = true;
                        }else{
                            $output .= chr($this->_digitValueToInt($t + (($q - $t) % ($this->_base - $t))));
                            $q = (int) (($q - $t) / ($this->_base - $t));
                            $k += $this->_base;
                        }
                    } while (!$my_end);

                    $output .= chr($this->_digitValueToInt($q));
                    $bias = $this->_adapt($delta, $h + 1, ($h == $b));
                    $delta = 0;
                    $h++;
                }
            }
            $delta++;
            $n++;
        }

        return ($this->_punycode_prefix . $output);
    }

    /**
    * Decode given Punycode-representation into it's string/domain name
    *
    * @param  string  $input    String/name to be decoded
    * @return string            Decoded form of the given string
    * @access private
    * @see    _punycodeEncode()
    */
    function _punycodeDecode($input)
    {
        if (substr($input, 0, strlen($this->_punycode_prefix)) != $this->_punycode_prefix){
            // string is not punycode-encoded
            return $input;
        }

        $input = substr($input, strlen($this->_punycode_prefix)); // remove punycode-prefix

        $n = $this->_initial_n;
        $i = 0;
        $bias = $this->_initial_bias;
        $output = "";

        $my_pos = strrpos ($input, '-');
        if ($my_pos !== false){
            $output .= substr($input, 0, $my_pos);
            $input = substr($input, $my_pos + 1);
        } while (strlen($input) > 0){
            $oldi = $i;
            $w = 1;
            $my_end = false;
            $k = $this->_base;
            do{
                $my_codepoint = substr($input, 0, 1);
                $input = substr($input, 1);
                $digit = $this->_digitIntToValue(ord($my_codepoint));
                $i = $i + $digit * $w; // fail on overflow
                if ($k <= $bias){
                    $t = $this->_tmin;
                }elseif ($k >= ($bias + $this->_tmax)){
                    $t = $this->_tmax;
                }else{
                    $t = $k - $bias;
                }
                if ($digit < $t){
                    $my_end = true;
                }else{
                    $w = $w * ($this->_base - $t); // fail on overflow
                    $k += $this->_base;
                }
            } while (!$my_end);

            $bias = $this->_adapt($i - $oldi, strlen($output) + 1, ($oldi == 0));
            $n = $n + (int) ($i / (strlen($output) + 1));
            $i = $i % (strlen($output) + 1);
            // if n is a basic code point then fail
            $output = substr($output, 0, $i) . chr($n) . substr($output, $i);
            $i++;
        }
        return ($output);
    }

    /**
    * Encode the given string/domainname using Punycode
    *
    * As a first step "nameprep" is applied to the input-string. Then all non-basic
    * code-points using the Punycode-algorithm.
    * !! Please note that this class does not use  full nameprep/punycode encoding.
    * This is caused by using only a limited set of characters that can be represented
    * using ASCII. If possible on your system we strongly suggest to use
    * I18N_Punycode_Unicode instead. !!
    *
    * @param  string $domainname  String/name to be encoded
    * @return string              Encoded form of the given string/name
    * @access public
    * @see    I18N_Punycode_Unicode
    */
    function encode($domainname)
    {
        $domainname_labels = explode('.', $domainname);

        foreach ($domainname_labels as $key => $value) {
            $domainname_labels[$key] = $this->_nameprep($domainname_labels[$key]);
            $domainname_labels[$key] = $this->_punycodeEncode($domainname_labels[$key]);
        }

        $domainname = implode(".", $domainname_labels);
        return $domainname;
    }

    /**
    * Decode the given string/domainname from Punycode
    *
    * !! Please note that this class does not use full punycode encoding.
    * This is caused by using only a limited set of characters that can be represented
    * using ASCII. If possible on your system we strongly suggest to use
    * I18N_Punycode_Unicode instead. !!
    *
    * @param  string $domainname  String/name to be decoded
    * @return string              Decoded form of the given string/name
    * @access public
    * @see    I18N_Punycode_Unicode
    */
    function decode($domainname)
    {
        $domainname_labels = explode('.', $domainname);

        foreach ($domainname_labels as $key => $value){
            $domainname_labels[$key] = $this->_punycodeDecode($domainname_labels[$key]);
        }

        $domainname = implode(".", $domainname_labels);
        return $domainname;
    }
} // class I18N_Punycode_ASCII

/**
* Class for encoding/decoding on international domainnames using Punycode; Unicode-mode
*
* Punycode is meant to be used with Unicode-strings. This ASCII-class is just provided for
* convenience if you can be sure you just need to encode strings with usual ASCII-characters.
*
* @author   Stefan Neufeind <pear.neufeind@speedpartner.de>
* @package  I18N_Punycode
* @access   public
* @see      I18N_Punycode_ASCII
*/
class I18N_Punycode_Unicode extends I18N_Punycode_ASCII
{
    /**
    * @access private
    */
    var $_punycode_prefix_ucs4 = "\x00\x00\x00x\x00\x00\x00n\x00\x00\x00-\x00\x00\x00-";
    /**
    * @access private
    */
    var $_punycode_delimiter_ucs4 = "\x00\x00\x00-";
    /**
    * @access private
    */
    var $_conversion_extension = "mbstring";

    static public function lazyDecode($str, $encoding = "utf-8"){
        if($str == "xn--80aesecbkjk5a1j.xn--p1ai") return "маньковский.рф"; //Жесткий Хак
		$obj = I18N_Punycode_Unicode::factory('mbstring'); // here we select the "mbstring" for character-conversion
		return $obj->decode($str, $encoding);    	
	}

    static public function lazyEncode($str, $encoding = "utf-8"){
		$obj = I18N_Punycode_Unicode::factory('mbstring'); // here we select the "mbstring" for character-conversion
		return $obj->encode($str, $encoding);    	
	}

    
    static public function d($str, $encoding = "utf-8"){
		return self::lazyDecode($str, $encoding);    	
	}
    
    static public function e($str, $encoding = "utf-8"){
		return self::lazyEncode($str, $encoding);    	
	}
	
	
    
    /**
    * Create a new I18N_Punycode_Unicode object
    *
    * This function exists because the I18N_Punycode_Unicode can interface with
    * various unicode-extensions and needs to be able to return error-objects
    * when a new object is created.
    *
    * @param string $extension    Unicode-extension to be used for character-conversions (currently only 'mbstring' supported)
    *
    * @return mixed   newly created I18N_Punycode_Unicode object, or a I18N_Punycode error code
    * @access public
    */
    function &factory($extension='auto')
    {
        // implement automatic extension-selection here
        // currently only mbstring and iconv work

        if (($extension != 'mbstring')) { //  && ($extension != 'iconv')
            require_once('PEAR.php');
            return PEAR::raiseError('No extension for character conversion available.', 1);
        }

        $obj = new I18N_Punycode_Unicode();
        $obj->_conversion_extension = $extension;

        return $obj;
    }

    /**
    * Convert an UCS-4-character (4 bytes) to an integer-value
    *
    * @param  string  $ucs4_input   UCS-4-string (4 characters/bytes)
    * @return int                   long int value for this UCS-4-character
    * @access private
    * @see    _intToUCS4()
    */
    function _UCS4ToInt($ucs4_input)
    {
        $val = ord($ucs4_input[0]);
        $val = $val * 256 + ord($ucs4_input[1]);
        $val = $val * 256 + ord($ucs4_input[2]);
        $val = $val * 256 + ord($ucs4_input[3]);
        return $val;
    }

    /**
    * Convert an to an UCS-4-character (4 bytes)
    *
    * @param  int     $int_input    long int value for this UCS-4-character
    * @return string                UCS-4-string (4 characters/bytes)
    * @access private
    * @see    _UCS4ToInt()
    */
    function _intToUCS4($int_input)
    {
        $ucs4 = "    ";
        $ucs4[3] = chr($int_input % 256);
        $int_input /= 256;
        $ucs4[2] = chr($int_input % 256);
        $int_input /= 256;
        $ucs4[1] = chr($int_input % 256);
        $int_input /= 256;
        $ucs4[0] = chr($int_input);
        return $ucs4;
    }

    /**
    * Splits a domainname into separate labels (as described in RFC3490)
    *
    * labels are separated by dots;
    * e.g. www.example.com is split into array('www', 'example', 'com');
    * the php-function mb_split is not used for this, since it's still declared "experimental"
    *
    * @param  string $ucs4_input  domainname in ucs4-unicode-encoding
    * @return array               array of labels (sub-strings), also in ucs4-encoding
    * @access private
    */
    function _splitNameIntoLabels($ucs4_input)
    {
        // special case: empty input string?
        if (strlen($ucs4_input) == 0){
            return array();
        }
        // otherwise split the string on each dot
        $arrayOfLabels = array();
        $tempLabel = "";
        $inputlength = strlen($ucs4_input);
        for($character_count = 0; $character_count < $inputlength; $character_count += 4) { // ucs4 is always 4 chars/bytes per unicode-char
            $one_ucs4_char = substr($ucs4_input, $character_count, 4);
            if ($one_ucs4_char == "\x00\x00\x00.") { // if it's a dot
                $arrayOfLabels[] = $tempLabel;
                $tempLabel = "";
            }else{
                $tempLabel .= $one_ucs4_char;
            }
        }
        $arrayOfLabels[] = $tempLabel;
        return $arrayOfLabels;
    }

    /**
    * Apply nameprep transformation to the given string
    *
    * Nameprep replaces/removes various characters according to given tables.
    * These tables have been carefully designed with the fact in mind that
    * certain strings usually look quite similar or can be written in different
    * variations.
    *
    * @param  string  $input    String/name to be decoded
    * @return string            Decoded form of the given string
    * @access private
    */
    function _nameprepUCS4($ucs4_input)
    {
        global $RFC3454_TABLE_B1_ucs4;
        global $RFC3454_TABLE_B2_ucs4;

        $ucs4_output = "";
        $inputlength_absolute = strlen($ucs4_input);
        for($character_count = 0; $character_count < $inputlength_absolute; $character_count += 4) { // ucs4 is always 4 chars/bytes per unicode-char
            $tempchar = substr($ucs4_input, $character_count, 4);
            // two-step stringprep according to the nameprep-profile defined in RFC3491
            if (isset($RFC3454_TABLE_B1_ucs4[$tempchar])){
                $tempchar = $RFC3454_TABLE_B1_ucs4[$tempchar];
            }
            if (isset($RFC3454_TABLE_B2_ucs4[$tempchar])){
                $tempchar = $RFC3454_TABLE_B2_ucs4[$tempchar];
            }

            $ucs4_output .= $tempchar;
        }

        return $ucs4_output;
    }

    /**
    * Encode given string/domain name into it's Punycode-representation
    *
    * @param  string  $input    String/name to be encoded (in UCS-4 format)
    * @return string            Punycode form of the given string (in UCS-4 format)
    * @access private
    * @see    _punycodeDecode()
    */
    function _punycodeEncode($ucs4_input)
    {
        // copy all basic code-points
        $output = "";
        $inputlength_absolute = strlen($ucs4_input);
        for($character_count = 0; $character_count < $inputlength_absolute; $character_count += 4) { // ucs4 is always 4 chars/bytes per unicode-char
            $tempchar = substr($ucs4_input, $character_count, 4);
            if ($this->_isBasicCodepoint($this->_UCS4ToInt($tempchar))){
                $output .= $tempchar;
            }
        }
        $output_len = (int) strlen($output) / 4; // input in UCS4 is exactly 4 chars per byte

        $numBasicCodepointsInInput = $output_len;
        // check for trivial case that there are no chars to encode
        if ($numBasicCodepointsInInput == (int) (strlen($ucs4_input) / 4)){
            return $output;
        }

        if ($numBasicCodepointsInInput > 0){
            $output .= $this->_punycode_delimiter_ucs4;
            $output_len++;
        }

        $n = $this->_initial_n;
        $delta = 0;
        $bias = $this->_initial_bias;
        $h = $b = $numBasicCodepointsInInput;

        $inputlength_ucs4 = (int) strlen($ucs4_input) / 4;
        while ($h < $inputlength_ucs4){
            // the minimum {non-basic} code point >= n in the input
            unset($m);
            $charcount = 0;

            do {
                $tempcode = $this->_UCS4ToInt(substr($ucs4_input, $charcount, 4));
                if (($tempcode >= $n) &&
                    (!isset($m) || ($tempcode <= $m))
                   ) {
                    $m = $tempcode;
                }
                $charcount += 4;
            } while ($charcount<strlen($ucs4_input));
            $delta = $delta + ($m - $n) * ($h + 1); // fail on overflow
            $n = $m;

            for ($charcount = 0; $charcount < $inputlength_absolute; $charcount += 4){
                $c = $this->_UCS4ToInt(substr($ucs4_input, $charcount, 4));
                if ($c < $n) {
                    $delta++; // fail on overflow
                } elseif ($c == $n) {
                    $q = $delta;
                    $my_end = false;
                    $k = $this->_base;
                    do{
                        if ($k <= $bias){
                            $t = $this->_tmin;
                        }elseif ($k >= ($bias + $this->_tmax)){
                            $t = $this->_tmax;
                        }else{
                            $t = $k - $bias;
                        }

                        if ($q < $t){
                            $my_end = true;
                        }else{
                            $output .= $this->_intToUCS4($this->_digitValueToInt($t + (($q - $t) % ($this->_base - $t))));
                            $output_len++;
                            $q = (int) (($q - $t) / ($this->_base - $t));
                            $k += $this->_base;
                        }
                    } while (!$my_end);

                    $output .= $this->_intToUCS4($this->_digitValueToInt($q));
                    $output_len++;
                    $bias = (int) $this->_adapt($delta, $h + 1, ($h == $b));
                    $delta = 0;
                    $h++;
                }
            }
            $delta++;
            $n++;
        }

        return ($this->_punycode_prefix_ucs4 . $output);
    }

    /**
    * Decode given Punycode-representation into it's string/domain name
    *
    * @param  string  $input    String/name to be decoded (in UCS-4 format)
    * @return string            Decoded form of the given string (in UCS-4 format)
    * @access private
    * @see    _punycodeEncode()
    */
    function _punycodeDecode($ucs4_input)
    {
        if (substr($ucs4_input, 0, strlen($this->_punycode_prefix_ucs4)) != $this->_punycode_prefix_ucs4){
            // string is not punycode-encoded
            return $ucs4_input;
        }

        $ucs4_input = substr($ucs4_input, strlen($this->_punycode_prefix_ucs4)); // remove punycode-prefix

        $n = $this->_initial_n;
        $i = 0;
        $bias = $this->_initial_bias;
        $output = "";

        $my_pos = strlen($ucs4_input)-4;
        $pos_found  = false;
        do
        {
            $sub = substr($ucs4_input, $my_pos, 4);
            if ($sub == "\x00\x00\x00-")
            {
                $pos_found = true;
            } else {
                $my_pos -= 4;
            }
        } while ((!$pos_found) && ($my_pos >= 0));
        if ($pos_found !== false)
        {
            $output .= substr($ucs4_input, 0, $my_pos);
            $ucs4_input = substr($ucs4_input, $my_pos + 4, strlen($ucs4_input) - $my_pos-4);
        }

        while (strlen($ucs4_input) > 0){
            $oldi = $i;
            $w = 1;
            $my_end = false;
            $k = $this->_base;
            do{
                $my_codepoint = $this->_UCS4ToInt(substr($ucs4_input, 0, 4));
                $ucs4_input = substr($ucs4_input, 4);
                $digit = $this->_digitIntToValue($my_codepoint);
                $i = $i + $digit * $w; // fail on overflow
                if ($k <= ($bias + $this->_tmin)){
                    $t = $this->_tmin;
                }elseif ($k >= ($bias + $this->_tmax)){
                    $t = $this->_tmax;
                }else{
                    $t = $k - $bias;
                }
                if ($digit < $t){
                    $my_end = true;
                }else{
                    $w = $w * ($this->_base - $t); // fail on overflow
                    $k += $this->_base;
                }
            } while (!$my_end);

            $tmp_length = (int) strlen($output)/4;
            $bias = $this->_adapt($i - $oldi, $tmp_length + 1, ($oldi == 0));
            $n = $n + (int) ($i / ($tmp_length + 1)); // fail on overflow
            $i = $i % ($tmp_length + 1);
            // if n is a basic code point then fail
            $output = substr($output, 0, $i*4) . $this->_intToUCS4($n) . substr($output, $i*4, ($tmp_length - $i)*4);
            $i++;
        }
        return ($output);
    }

    /**
    * Encode the given string/domainname using Punycode
    *
    * As a first step "nameprep" is applied to the input-string. Then all non-basic
    * code-points using the Punycode-algorithm.
    *
    * @param  string $domainname  String/name to be encoded
    * @param  string $encoding    Character encoding which the input/output strings use; if not set auto-detect is tried;
    *                             as last resort ISO-8859-1 is used
    * @return string              Encoded form of the given string/name
    * @access public
    * @see    I18N_Punycode_Unicode
    */
    function encode($domainname, $encoding = null)
    {
        if ($this->_conversion_extension == "mbstring") {
            if (!isset($encoding) && !($encoding = mb_internal_encoding())) {
                $encoding = 'ISO-8859-1';
            }
            $domainname_ucs4 = mb_convert_encoding($domainname, 'UCS-4', $encoding);
        } elseif ($this->_conversion_extension == "iconv") {
            if (!isset($encoding) && !($encoding = iconv_get_encoding("internal_encoding"))) {
                $encoding = 'ISO-8859-1';
            }
            $domainname_ucs4 = iconv($encoding, 'UCS-4', $domainname);
        } else {
            return false;
        }

        $domainname_ucs4_labels = $this->_splitNameIntoLabels($domainname_ucs4);

        foreach ($domainname_ucs4_labels as $key => $value){
            $domainname_ucs4_labels[$key] = $this->_nameprepUCS4($domainname_ucs4_labels[$key]);
            $domainname_ucs4_labels[$key] = $this->_punycodeEncode($domainname_ucs4_labels[$key]);
        }

        $domainname_ucs4 = implode("\x00\x00\x00.", $domainname_ucs4_labels);

        if ($this->_conversion_extension == "mbstring") {
            $domainname = mb_convert_encoding($domainname_ucs4, $encoding, 'UCS-4');
        } elseif ($this->_conversion_extension == "iconv") {
            $domainname = iconv('UCS-4', $encoding, $domainname_ucs4);
        }
        return $domainname;
    }

    /**
    * Decode the given string/domainname from Punycode
    *
    * @param  string $domainname  String/name to be decoded
    * @param  string $encoding    Character encoding which the input/output strings use; if not set auto-detect is tried;
    *                             as last resort ISO-8859-1 is used
    * @return string              Decoded form of the given string/name
    * @access public
    * @see    I18N_Punycode_Unicode
    */
    function decode($domainname, $encoding = null)
    {
        if ($this->_conversion_extension == "mbstring") {
            if (!isset($encoding) && !($encoding = mb_internal_encoding())){
                $encoding = 'ISO-8859-1';
            }
            $domainname_ucs4 = mb_convert_encoding($domainname, 'UCS-4', $encoding);
        } elseif ($this->_conversion_extension == "iconv") {
            if (!isset($encoding) && !($encoding = iconv_get_encoding("internal_encoding"))) {
                $encoding = 'ISO-8859-1';
            }
            $domainname_ucs4 = iconv($encoding, 'UCS-4', $domainname);
        } else {
            return false;
        }

        $domainname_ucs4_labels = $this->_splitNameIntoLabels($domainname_ucs4);

        foreach ($domainname_ucs4_labels as $key => $value){
            $domainname_ucs4_labels[$key] = $this->_punycodeDecode($domainname_ucs4_labels[$key]);
        }

        $domainname_ucs4 = implode("\x00\x00\x00.", $domainname_ucs4_labels);

        if ($this->_conversion_extension == "mbstring") {
            $domainname = mb_convert_encoding($domainname_ucs4, $encoding, 'UCS-4');
        } elseif ($this->_conversion_extension == "iconv") {
            $domainname = iconv('UCS-4', $encoding, $domainname_ucs4);
        }
        return $domainname;
    }
} // class I18N_Punycode_Unicode

?>
